﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IGolovkine.ShoutCastRipper.Service.Configuration;
using IGolovkine.Core;
using IGolovkine.ShoutCastRipper.Filters;
using IGolovkine.Core.Linq;
using IGolovkine.ShoutCastRipper.Service.Contracts;
using IGolovkine.Core.Diagnostics;
namespace IGolovkine.ShoutCastRipper.Service
{
    public class StationRipperService : IStationRipperService
    {
        private readonly ILogWriter _serviceLogWriter;
        private readonly Dictionary<string, IStationRipper> _rippers;
        public event EventHandler<StationRipperEventArgs> StationRipperStateChanged;

        public StationRipperService(StationRipperServiceSection configSection)
        {
            _serviceLogWriter = this.CreateServiceLogWriter(configSection);
            _rippers = new Dictionary<string, IStationRipper>();

            foreach (var ripperConfig in configSection.StationRippers)
            {
                if (_rippers.Keys.Contains(ripperConfig.Name))
                {
                    throw new ArgumentException(string.Format("Another instance of '{0}' already exists", ripperConfig.Name));
                }

                var stationLogWriter = this.CreateStationRipperLogWriter(ripperConfig);
                var connectionFactory = this.CreateStationConnectionFactory(ripperConfig);
                var clientFactory = this.CreateStationClientFactory(ripperConfig, connectionFactory, stationLogWriter);
                var probingPolicy = this.CreateContentTitleProbingPolicy(ripperConfig, stationLogWriter);
                var contentRepository = this.CreateContentRepository(ripperConfig, stationLogWriter);
                var lengthPolicy = this.CreateContentLengthPolicy(ripperConfig, stationLogWriter);

                var ripper = this.CreateStationRipper(ripperConfig, clientFactory, contentRepository, probingPolicy, lengthPolicy, stationLogWriter);
                ripper.StatusChanged += new EventHandler<StationRipperStatusChangedEventArgs>(OnRipperStateChanged);
                ripper.ContentTitleChanged +=new EventHandler<StationRipperContentTitleChangedEventArgs>(OnRipperStateChanged);
                ripper.UrlChanged += new EventHandler<StationRipperUrlChangedEventArgs>(OnRipperStateChanged);
                _rippers.Add(ripperConfig.Name, ripper);
            }
        }

        protected void OnRipperStateChanged(object sender, StationRipperEventArgs e)
        {
            if (this.StationRipperStateChanged != null)
            {
                this.StationRipperStateChanged(sender, e);
            }
        }        
      
        public IEnumerable<IStationRipper> StationRippers
        {
            get { return _rippers.Values; }
        }

        public void Start()
        {
            _rippers.Values.Where(r => r.Status == StationRipperStatus.Stopped).ForEach(r => r.Start());
        }

        public void Stop()
        {
            _rippers.Values.Where(r => r.Status != StationRipperStatus.Stopped).ForEach(r => r.Stop());
        }

        protected virtual ILogWriter CreateServiceLogWriter(StationRipperServiceSection p)
        {
            CompositeLogWriter logWriter = new CompositeLogWriter();
            logWriter.LogWriters.Add(new ConsoleLogWriter("System"));
            logWriter.LogWriters.Add(new FileLogWriter(p.LoggerParameter));
            return logWriter;
        }

        protected virtual ILogWriter CreateStationRipperLogWriter(StationRipperElement p)
        {
            CompositeLogWriter logWriter = new CompositeLogWriter();
            logWriter.LogWriters.Add(new ConsoleLogWriter(p.Name));
            logWriter.LogWriters.Add(new FileLogWriter(p.LoggerParameter));
            return logWriter;
        }

        protected virtual TypeFactory<IStationConnection, string> CreateStationConnectionFactory(StationRipperElement p)
        {
            return (url) => new StationConnection(url, p.UserAgent);
        }

        protected virtual TypeFactory<IStationClient, string> CreateStationClientFactory(StationRipperElement p, TypeFactory<IStationConnection, string> connectionFactory, ILogWriter logWriter)
        {
            return url=> new StationClient(u=> new StationConnection(url, p.UserAgent));
        }

        protected virtual IContentTitleProbingPolicy CreateContentTitleProbingPolicy(StationRipperElement p, ILogWriter logWriter)
        {
            return new ContentTitleProbingPolicy(p.ContentTitleProbingFrequency, logWriter);
        }

        protected virtual IContentRepository CreateContentRepository(StationRipperElement p, ILogWriter logWriter)
        {
            IEnumerable<string> lookUpFolders = p.FileRepository.LookUpFolders.ConvertAll(f => f.Path);
            ContentFileRepository contentRepository = new ContentFileRepository(p.FileRepository.Path, lookUpFolders);
            foreach (FileCommandElement el in p.FileRepository.FileCommands)
            {
                contentRepository.FileCommands.Add(new FileCommand(el.FileName, el.Arguments, el.WorkingDirectory, el.UserName, el.Password, el.Domain));
            }
            contentRepository.FileCommandExecutionError += new EventHandler<FileCommandExecutionErrorEventArgs>(OnContentRepositoryFileCommandExecutionError);
            return contentRepository;
        }

        private void OnContentRepositoryFileCommandExecutionError(object sender, FileCommandExecutionErrorEventArgs e)
        {
            _serviceLogWriter.WriteException(string.Format("Error executing file command: '{0}'", e.FileCommand), e.Exception);
        }

        protected virtual IContentLengthPolicy CreateContentLengthPolicy(StationRipperElement p, ILogWriter logWriter)
        {
            return new ContentLengthPolicy(p.MinimumLength, p.MaximumLength);
        }

        protected virtual IStationRipper CreateStationRipper(StationRipperElement p, TypeFactory<IStationClient,string> stationClientFactory,
           IContentRepository contentRepository, IContentTitleProbingPolicy contentProbingPolicy,
           IContentLengthPolicy lengthPolicy, ILogWriter logWriter)
        {
            StationRipper stationRipper = new StationRipper(p.Name, p.Url, stationClientFactory, contentRepository, contentProbingPolicy, lengthPolicy, logWriter);
            IContentTitleDateExtractor dataExtractor = new ContentTitleDateExtractor(p.DatePatterns.ConvertAll(e => e.Regex));
            stationRipper.TitleFilters.Add(new ContentTitleAgeFilter(p.MaximumAge, dataExtractor));

            List<ContentTitleRegexFilterDefintion> regexDefinitions = new List<ContentTitleRegexFilterDefintion>();
            foreach (ContentTitleRegexFilterElement el in p.ContentTitleRegexFilters)
            {
                try
                {
                    ContentTitleRegexFilterType type = (ContentTitleRegexFilterType) Enum.Parse(typeof(ContentTitleRegexFilterType), el.Type, true);
                    ContentTitleRegexFilterDefintion definition = new ContentTitleRegexFilterDefintion(el.Regex, type);
                    regexDefinitions.Add(definition);
                }
                catch (Exception ex)
                {
                    string values = string.Join(" or ", Enum.GetNames(typeof(ContentTitleRegexFilterType)));
                    throw new ArgumentException(string.Format("'{0}' doesn't fall within the range of allowed values for {1}. The value has to be {2}", el.Type, typeof(ContentTitleRegexFilterType).Name, values), ex);
                }
            }

            stationRipper.TitleFilters.Add(new ContentTitleRegexFilter(regexDefinitions, false));
            stationRipper.TitleFilters.Add(new ContentTitleDuplicatesFilter(contentRepository));
            
            return stationRipper;
        }

        public void Dispose()
        {
            _rippers.Values.ForEach(r => r.Dispose());
            _rippers.Clear();
        }        
    }
}
